<?php
if(!defined('BASEPATH')) exit('No direct script access allowed');
/* 
	微信PHPSDK
*/
class Wechat{
	
	protected $url_api_host = 'https://api.weixin.qq.com/cgi-bin/';
	protected $url_accesstoken = 'token';
	protected $url_jsticket = 'ticket/getticket';
	protected $url_userinfo = 'user/info';
	
	// 授权
	protected $url_oauth_host = 'https://api.weixin.qq.com/sns/';
	protected $url_oauth_authorize = 'https://open.weixin.qq.com/connect/oauth2/authorize';
	protected $url_oauth_accesstoken = 'oauth2/access_token';
	protected $url_oauth_userinfo = 'userinfo';
	protected $url_oauth_check = 'auth';
	
	// 素材
	protected $url_upload_forevermedia = 'material/add_material';
	protected $url_upload_media = 'media/upload';
	
	protected $url_getcallbackip = 'getcallbackip';
	protected $url_qrcode_create = 'qrcode/create';
	
	protected $appid;
	protected $appsecret;
	protected $jsapi_ticket;
	
	public $oauth_accesstoken;
	public $access_token;
	
	// 缓存
	public $cache_path;
	private $acstoken_cachename;
	private $oautoken_cachename;
	private $jsticket_cachename;
	
	public $error;
	
	protected $_config;
	
	public function __construct($option = array(), $confname = 'wechat'){
		$CI = &get_instance();
		$CI->load->config($confname, true);
		$config = $CI->config->item($confname);
		if($option){
			foreach($option as $key => $value){
				$config[$key] = $value;
			}
		}
		if(!$config['appid']){
			exit('appid cannot empty');
		}
		$this->_config = $config;
		$this->appid = $config['appid'];
		$this->appsecret = $config['appsecret'];
		$this->cache_path = APPCOMMON.'cache/wechat/';
		$this->acstoken_cachename = 'WxAccessToken_'.$this->appid;
		$this->oautoken_cachename = 'WxOauthToken_'.$this->appid;
		$this->jsticket_cachename = 'WxJsapiTicket_'.$this->appid;
	}
	
	// 获取全局唯一接口调用凭据access_token
	public function get_accesstoken(){
		$res = $this->get_cache($this->acstoken_cachename);
		if($res){
			$this->access_token = $res;
			$rs = $this->request($this->url_api_host.$this->url_getcallbackip.'?access_token='.$res);
			$rs = json_decode($rs, true);
			if($rs && !$rs['errcode']){
				return $res;
			}
		}
		$req = $this->request($this->url_api_host.$this->url_accesstoken, [
			'grant_type' => 'client_credential',
			'appid' => $this->appid,
			'secret' => $this->appsecret
		]);
		if(!$req){
			return false;
		}
		$result = json_decode($req, true);
		if(!$result || $result['errcode']){
			$this->error = $result;
			return false;
		}
		$this->access_token = $result['access_token'];
		$this->set_cache($this->acstoken_cachename, $this->access_token, $result['expires_in'] - 100);
		return $this->access_token;
	}
	
	// 获取用户基本信息(UnionID机制)、带关注状态
	# 用户没有关注该公众号，拉取不到其余信息
	public function get_userinfo($openid){
		if(!$openid || (!$this->access_token && !$this->get_accesstoken())){
			return false;
		}
		$req = $this->request($this->url_api_host.$this->url_userinfo, [
			'access_token' => $this->access_token,
			'openid' => $openid,
			'lang' => 'zh_CN'
		]);
		if(!$req){
			return false;
		}
		$result = json_decode($req, true);
		if(!$result || $result['errcode']){
			$this->remove_cache($this->acstoken_cachename);
			$this->error = $result;
			return false;
		}
		return $result;
	}
	
	// 获取JS接口的临时票据jsapi_ticket
	public function get_jsticket(){
		$res = $this->get_cache($this->jsticket_cachename);
		if($res){
			$this->jsapi_ticket = $res;
			return $res;
		}
		if(!$this->access_token && !$this->get_accesstoken()){
			return false;
		}
		$req = $this->request($this->url_api_host.$this->url_jsticket, [
			'access_token' => $this->access_token,
			'type' => 'jsapi'
		]);
		if(!$req){
			return false;
		}
		$result = json_decode($req, true);
		if(!$result || $result['errcode']){
			$this->error = $result;
			return false;
		}
		$this->jsapi_ticket = $result['ticket'];
		$this->set_cache($this->jsticket_cachename, $this->jsapi_ticket, $result['expires_in'] - 100);
		return $this->jsapi_ticket;
	}
	
	// 授权code重定向
	public function get_oauth_redirect($redirect = '', $scope = 'snsapi_userinfo', $state = 'state'){
		$url = $redirect ? $redirect : $this->get_current_url();
		return $this->url_oauth_authorize.'?appid='.$this->appid.'&redirect_uri='.urlencode($url).'&response_type=code&scope='.$scope.'&state='.$state.'#wechat_redirect';
	}
	
	// 授权获取access_token
	public function get_oauth_accesstoken($redirect = '', $code = ''){
		if($redirect === false && !$code){
			return false;
		}else{
			$code = isset($_GET['code']) ? htmlspecialchars($_GET['code']) : '';
			if(!$code){
				$url = $redirect ? $redirect : $this->get_oauth_redirect();
				header("Location: $url");
				return false;
			}
		}
		$req = $this->request($this->url_oauth_host.$this->url_oauth_accesstoken, [
			'appid' => $this->appid,
			'secret' => $this->appsecret,
			'code' => $code,
			'grant_type' => 'authorization_code'
		]);
		if(!$req){
			return false;
		}
		$result = json_decode($req, true);
		if(!$result || $result['errcode']){
			$this->error = $result;
			return false;
		}
		$this->oauth_accesstoken = $result['access_token'];
		return $result;
	}
	
	// 检验授权凭证（access_token）是否有效
	public function check_oauth($openid, $access_token = ''){
		if(!$openid){
			return false;
		}
		$req = $this->request($this->url_oauth_host.$this->url_oauth_check, [
			'access_token' => ($access_token ? $access_token : $this->oauth_accesstoken),
			'openid' => $openid
		]);
		if(!$req){
			return false;
		}
		$result = json_decode($req, true);
		if(!$result || $result['errcode']){
			$this->error = $result;
			return false;
		}
		return true;
	}
	
	// 授权获取用户个人信息（UnionID 机制），不带关注状态
	public function get_oauth_userinfo($openid, $access_token = ''){
		if(!$openid){
			return false;
		}
		$req = $this->request($this->url_oauth_host.$this->url_oauth_userinfo, [
			'access_token' => ($access_token ? $access_token : $this->oauth_accesstoken),
			'openid' => $openid,
			'lang' => 'zh_CN'
		]);
		if(!$req){
			return false;
		}
		$result = json_decode($req, true);
		if(!$result || $result['errcode']){
			$this->error = $result;
			return false;
		}
		return $result;
	}
	
	public function get_cache($name){
		$file = $this->cache_path.$name;
		if(!is_file($file)){
			return false;
		}
		$res = json_decode(file_get_contents($file), true);
		if($res['expire'] > 0 && $res['expire'] < time()){
			$this->remove_cache($name);
			return false;
		}
		return $res['data'];
	}
	
	public function set_cache($name, $value, $expire){
		if(!is_dir($this->cache_path)){
			mkdir($this->cache_path, 0775, true);
		}
		$file = $this->cache_path.$name;
		if(!is_file($file)){
			file_put_contents($file, '');
			chmod($file, 0775);
		}
		$content = array(
			'expire' => time() + $expire,
			'data' => $value
		);
		$fp = fopen($file, 'w');
		fwrite($fp, json_encode($content));
		fclose($fp);
	}
	
	public function remove_cache($name){
		unlink($this->cache_path.$name);
	}
	
	public function get_current_url(){
		$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? 'https://' : 'http://';
		return "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
	}
	
	// 上传永久素材
	# type	媒体文件类型，分别有图片（image）、语音（voice）、视频（video）和缩略图（thumb）
	# 上传视频需要：array('title'=>'视频标题', 'introduction'=>'描述')
	public function upload_forevermedia($type, $params){
		if(!$this->access_token && !$this->get_accesstoken()){
			return false;
		}
		$data = array(
			'curl_upload' => 1,
			'media' => '@'.$params['file']
		);
		if('video' == $type){
			$data['description'] = json_encode([
				'title' => $params['title'],
				'introduction' => $params['desc']
			]);
		}
		$req = $this->request($this->url_api_host.$this->url_upload_forevermedia.'?access_token='.$this->access_token.'&type='.$type, $data, 'POST');
		if(!$req){
			return false;
		}
		$result = json_decode($req, true);
		if(!$result || $result['errcode']){
			$this->error = $result;
			return false;
		}
		return $result;
	}
	
	// 上传临时素材
	# type	媒体文件类型，分别有图片（image）、语音（voice）、视频（video）和缩略图（thumb）
	# 上传视频需要：array('title'=>'视频标题', 'introduction'=>'描述')
	public function upload_media($type, $params){
		if(!$this->access_token && !$this->get_accesstoken()){
			return false;
		}
		$data = array(
			'curl_upload' => 1,
			'media' => '@'.$params['file']
		);
		if('video' == $type){
			$data['description'] = json_encode([
				'title' => $params['title'],
				'introduction' => $params['desc']
			]);
		}
		$req = $this->request($this->url_api_host.$this->url_upload_media.'?access_token='.$this->access_token.'&type='.$type, $data, 'POST');
		if(!$req){
			return false;
		}
		$result = json_decode($req, true);
		if(!$result || $result['errcode']){
			$this->error = $result;
			return false;
		}
		return $result;
	}
	
	// 创建二维码ticket
	#  $scene_id 场景id
	#  $expire 有效时间
	#		QR_SCENE为临时的整型参数值
	#		QR_LIMIT_SCENE为永久的整型参数值
	#		QR_STR_SCENE为临时的字符串参数值
	#		QR_LIMIT_STR_SCENE为永久的字符串参数值
	public function create_qrcode($scene_id, $expire = 0){
		if(!isset($scene_id) || (!$this->access_token && !$this->get_accesstoken())){
			return false;
		}
		if(is_int($scene_id)){
			$scene_data = array(
				'scene_id' => $scene_id
			);
			$action = ($expire > 0) ? 'QR_SCENE' : 'QR_LIMIT_SCENE';
		}else{
			$scene_data = array(
				'scene_str' => $scene_id
			);
			$action = ($expire > 0) ? 'QR_STR_SCENE' : 'QR_LIMIT_STR_SCENE';
		}
		$data = json_encode([
			'action_name' => $action,
			'action_info' => [
				'scene' => $scene_data
			]
		]);
		if($expire > 0){
			$data['expire_seconds'] = $expire;
		}
		$req = $this->request($this->url_api_host.$this->url_qrcode_create.'?access_token='.$this->access_token, $data, 'POST');
		if(!$req){
			return false;
		}
		$result = json_decode($req, true);
		if(!$result || $result['errcode']){
			$this->error = $result;
			return false;
		}
		$result['imgurl'] = 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='.$result['ticket'];
		return $result;
	}
	
	public function request($url, $params = array(), $method = 'GET', $header = null){
		if(!function_exists('curl_init')){
			return '';
		}
		$method = strtoupper($method);
		$curl = curl_init();
		curl_setopt($curl, CURLOPT_TIMEOUT, 60);
		curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
		curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
		if($header){
			curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
		}
		if(isset($params['ssl_cert']) && isset($params['ssl_key'])){
			curl_setopt($curl, CURLOPT_SSLCERT, $params['ssl_cert']);
			curl_setopt($curl, CURLOPT_SSLKEY, $params['ssl_key']);
			unset($params['ssl_cert'], $params['ssl_key']);
			$params = $params['data'];
		}
		if(isset($params['curl_upload']) && $params['curl_upload'] === 1){
			if(class_exists('\CURLFile')){
				curl_setopt($curl, CURLOPT_SAFE_UPLOAD, true);
				foreach($params as &$val){
					if(substr($val, 0, 1) == '@'){
						$val = new \CURLFile(ltrim($val, '@'));
					}
				}
			}else{
				if (defined('CURLOPT_SAFE_UPLOAD')) {
					curl_setopt($curl, CURLOPT_SAFE_UPLOAD, false);
				}
			}
			unset($params['curl_upload']);
		}
		switch($method){
			case 'GET':
				if($params){
                    $url = $url.(strpos($url, '?') ? '&' : '?').(is_array($params) ? http_build_query($params) : $params);
                }
				break;
			case 'POST':
				curl_setopt($curl, CURLOPT_POST, 1);
				if($params){
					curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
				}
				break;
		}
		curl_setopt($curl, CURLOPT_URL, $url);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
		curl_setopt($curl, CURLOPT_AUTOREFERER, 1);
		curl_setopt($curl, CURLOPT_MAXREDIRS, 4);
		$res = curl_exec($curl);
		curl_close($curl);
		return $res;
	}
	
	// 随机字符串
	public function rand_str($length = 16){
		$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
		$str = '';
		for ($i = 0; $i < $length; $i++) {
			$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
		}
		return $str;
	}
	
	// 生成唯一id
	public function unique_id(){
		return date('Ymd').sprintf('%05d', date('G')*3600 + date('i')*60 + date('s')).substr(microtime(), 2, 5).sprintf('%02d', rand(0, 99));
	}
	
	public function array_to_xml($arr){
		$xml = '<xml>';
		$xml .= $this->xml_body($arr);
		$xml .= '</xml>';
		return $xml;
	}
	
	private function filter_xml_str($str){
		return is_numeric($str) ? $str : '<![CDATA['.preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $str).']]>';
	}
	
	private function xml_body($arr){
		$xml = '';
		foreach($arr as $key => $val){
			is_numeric($key) && $key = "item id=\"$key\"";
			$xml .= "<$key>";
			$xml .= (is_array($val) || is_object($val)) ? $this->xml_body($val) : $this->filter_xml_str($val);
			list($key, ) = explode(' ', $key);
			$xml .= "</$key>";
		}
		return $xml;
	}
	
	public function xml_to_array($xml){
		if(!$xml){
			return array();
		}
        libxml_disable_entity_loader(true);
        $return = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
		return $return;
    }
	
}